Skip to content

Dynamic Key Generation

In the last module, we learned about React keys. When iterating over data with .map, we need to give each React element a unique key attribute so that React knows which DOM operations to trigger between renders.

In the earlier examples, our data conveniently came with unique tokens for each item, and we were able to use those tokens:

const inventory = [
{
id: 'abc123',
name: 'Soft-boiled egg press',
},
{
id: 'def456',
name: 'Hello Kitty toothbrush',
},
];
// We can use the `id` field in our iterations:
inventory.map(item => (
<ShopItem key={item.id} item={item} />
));

But what if our data doesn't have a unique token we can use?

In fact, this is one of the most common questions that developers have. React needs a unique value for each item, but we don't always have one!

Let's explore with an example.

Stickers!

In the sandbox below, you can click to produce randomized stickers:

The core logic has been completed, but we're getting a key warning in the console! Let's fix it.

I'll walk through exactly how this code works and how we can solve this problem, but I'd encourage you to spend a few minutes tinkering with it yourself, to see if you can understand what's going on and come up with any solutions.

The goal is to fix the key warning by dynamically generating the keys for each sticker.

Code Playground

import React from 'react';

import styles from './StickerPad.module.css';
import { getSticker } from './Stickers.data';

function StickerPad() {
const [stickers, setStickers] = React.useState([]);

return (
<button
className={styles.wrapper}
onClick={(event) => {
const stickerData = getSticker();
const newSticker = {
...stickerData,
x: event.clientX,
y: event.clientY,
};

const nextStickers = [...stickers, newSticker];
setStickers(nextStickers);
}}
>
{stickers.map((sticker) => (
<img
className={styles.sticker}
src={sticker.src}
alt={sticker.alt}
style={{
left: sticker.x,
top: sticker.y,
width: sticker.width,
height: sticker.height,
}}
/>
))}
</button>
);
}

export default StickerPad;

This code is pretty complex. Let's talk about it, and see how we can fix the key warning:

Video Summary

  • First, we go over how the code works:
    • clicking in the viewport pulls a random "sticker" object, adds the user's X/Y cursor position, and pushes it into the stickers state array (using the spread operator to prevent mutation).
    • We render the array of stickers, specifying an <img> tag for each one, and applying the data from the sticker object.
  • We haven't set any sort of key attribute yet, and it's not obvious what we should use. If we try to use src, we'll run into duplicates, since we only have a finite number of sticker shapes and the user can add unlimited stickers.
  • Even if we try to create a "compound" key, combining all of the data we have, it still isn't guaranteed to be enough! The user can place multiple stickers in exactly the same location.
  • What if we set the key equal to Math.random()? Well, that solves the warning, but it's a really bad idea.
    • Every time we add a new sticker, our component re-renders, running all of this code again. This means that on every render, each of our sticker images is given a brand new key (since Math.random() produces a different number every time!)
    • When React sees that an item's key changes, it assumes that it's a brand new item! Even if all of the properties are exactly the same, React responds by bulldozing the element and creating a brand new one from scratch.
    • This means that if we have 10 stickers in our array and we add an 11th, React deletes 10 images and creates 11 images, instead of creating a single image and not touching the 10 that haven't changed.
    • Interacting with the DOM is one of the slowest things we can do in JavaScript, and so we really want to minimize it.
  • The core idea of assigning each sticker a random value a good idea, but it's problematic when we re-assign that value on each render.
  • Instead, what if we create a random ID when the sticker is created, within the onClick handler?
<button
className={styles.wrapper}
onClick={(event) => {
const stickerData = getSticker();
const newSticker = {
...stickerData,
x: event.clientX,
y: event.clientY,
// Generate the random number on click, not during render!
id: Math.random(),
};
const nextStickers = [...stickers, newSticker];
setStickers(nextStickers);
}}
>
  • With this approach, we don't regenerate the ID on every render. This works much better!
  • There are lots of ways to create a unique identifier. In addition to Math.random(), we can also use crypto.randomUUID(), which produces a longer and alpha-numeric random value.

Here's the final code from the video:

function StickerPad() {
const [stickers, setStickers] = React.useState([]);
return (
<button
className={styles.wrapper}
onClick={(event) => {
const stickerData = getSticker();
const newSticker = {
...stickerData,
x: event.clientX,
y: event.clientY,
// Come up with a unique value for this sticker.
id: crypto.randomUUID(),
};
const nextStickers = [...stickers, newSticker];
setStickers(nextStickers);
}}
>
{stickers.map((sticker) => (
<img
// Use that previously-created unique value
// for the key:
key={sticker.id}
className={styles.sticker}
src={sticker.src}
alt={sticker.alt}
style={{
left: sticker.x,
top: sticker.y,
width: sticker.width,
height: sticker.height,
}}
/>
))}
</button>
);
}